/*
 * Created by Jaren at 2022/7/19 16:30
 */
import 'dart:async';

import 'package:inmotion_protocol/base/file/file_manager.dart';
import 'package:inmotion_protocol/base/protocol.dart';
import 'package:inmotion_protocol/common/event.dart';
import 'package:inmotion_protocol/common/mode.dart';
import 'package:inmotion_protocol/common/vehicle_info.dart';
import 'package:inmotion_protocol/protocol_worker.dart';
import 'package:inmotion_protocol/util/byte_util.dart';
import 'package:inmotion_protocol/util/device.dart';

abstract class LogFileManager {
  final Protocol protocol;
  LogConfig? _config;

  bool canceled = true;

  IFileManager? _fileManager;

  StreamController<UploadEvent> streamController = StreamController();

  LogFileManager(this.protocol);

  config(LogConfig config) {
    _fileManager = createFileDownloadManager(config);
    _fileManager!.init(config);
    _config = config;
  }

  IFileManager createFileDownloadManager(LogConfig config);

  Future<bool> checkModeForUpload() {
    if (!protocol.productProperty.model.isEuc()) return Future.value(true);

    return protocol.requestRealTimeState().then((state) {
      return state.pcMode != Mode.working;
    });
  }

  Stream<UploadEvent> start() {
    canceled = false;

    checkModeForUpload().then((shouldContinue) {
      if (!shouldContinue) {
        streamController.addError(UploadError.byModeCheck(UploadError.errorNotNow));
        return;
      }
      _fileManager!.start().listen((fileEvent) {
        switch (fileEvent.type) {
          case FileEvent.fileOpened:
            streamController.add(UploadEvent.byFileOpened());
            break;
          case FileEvent.fileProgress:
            streamController.add(FileTransferringEvent(fileEvent as FileProgressEvent));
            break;
          case FileEvent.prepareFileChecking:
            break;
          case FileEvent.fileChecked:
            streamController.add(UploadEvent.byFileChecked());
            break;
          case FileEvent.fileClosed:
            streamController.add(UploadEvent.byFileClosed());
            // 文件关闭，传输完成
            var finishedEvent = FinishedEvent(UploadEvent.typeFinished);
            finishedEvent.putLogFile(DeviceUtils.getHmicDevice(protocol.productProperty.model), _config!.localFile);
            streamController.add(finishedEvent);
            break;
        }
      }).onError((error) {
        if (error is CanceledError) {
          streamController.add(UploadEvent.byCanceled());
          return;
        }
        if (error is FileOperationStepError) {
          streamController.addError(UploadError.byFileOperationStepError(error));
          return;
        }
        streamController.addError(error);
      });
    }).onError((error, stackTrace) {
      if (error is MsgTimeoutException) {
        streamController.addError(UploadError.byModeCheck(UploadError.errorTimeout));
        return;
      }
      streamController.addError(error ?? Exception("Unknown exception."));
    });

    return streamController.stream;
  }

  void cancel() {
    canceled = true;
    if (_fileManager != null) _fileManager!.interrupt();
  }

  LogConfig createLogConfig(String localFilePath, {int? target, ProductProperty? productProperty});

  void destroy() {
    try {
      cancel();
      streamController.close();
    } catch (ignore) {}
  }
}

class LogConfig extends FileConfig {}

class UploadEvent {
  static const typeCanceled = 0;
  static const typeFileOpened = 1;
  static const typeFileTransferring = 2;
  static const typeFileChecked = 3;
  static const typeFileClosed = 4;
  static const typeFinished = 5;

  int type = -1;

  UploadEvent(this.type);

  UploadEvent.byCanceled() : this(typeCanceled);

  UploadEvent.byFileOpened() : this(typeFileOpened);

  UploadEvent.byFileChecked() : this(typeFileChecked);

  UploadEvent.byFileClosed() : this(typeFileClosed);

  UploadEvent.byFinished() : this(typeFinished);

  @override
  String toString() {
    return 'UploadEvent{type: $type}';
  }
}

class FileTransferringEvent extends UploadEvent {
  final FileProgressEvent _fileProgressEvent;

  FileTransferringEvent(this._fileProgressEvent) : super(UploadEvent.typeFileTransferring);

  /// 当前进度
  int progress() {
    return _fileProgressEvent.progress;
  }

  /// 总大小
  int total() {
    return _fileProgressEvent.total;
  }

  /// 平均速度
  int aveSpeed() {
    return _fileProgressEvent.aveSpeed;
  }

  /// 实时速度
  int rtSpeed() {
    return _fileProgressEvent.rtSpeed;
  }

  /// 已用时间
  int costSeconds() {
    return _fileProgressEvent.costSeconds;
  }

  /// 预计剩余时间
  int remainSeconds() {
    return _fileProgressEvent.remainSeconds;
  }

  @override
  String toString() {
    return 'FileTransferringEvent{_fileProgressEvent: $_fileProgressEvent}';
  }
}

class FinishedEvent extends UploadEvent with LogFinishedEvent {
  FinishedEvent(super.type);
}

class UploadError extends Error {
  int step;

  int? error;

  FileOperationStepError? fileOperationStepError;

  static const int stepModeCheck = 1;
  static const int stepGetLogInfo = 2;
  static const int stepFileOperation = 3;

  static const int errorNotNow = 0x3;
  static const int errorTimeout = 0xFF;

  UploadError(this.step, this.error);

  static UploadError byModeCheck(int error) {
    return UploadError(stepModeCheck, error);
  }

  static UploadError byGetLogInfo(int error) {
    return UploadError(stepGetLogInfo, error);
  }

  static UploadError byFileOperationStepError(FileOperationStepError error) {
    var uploadError = UploadError(stepFileOperation, null);
    uploadError.fileOperationStepError = error;
    return uploadError;
  }

  bool isNotNowError() {
    return step == stepModeCheck && error == errorNotNow;
  }

  /// 使用分隔符[split]格式化错误码
  /// 一般用于显示
  String formatErrorCode([String split = ', ']) {
    StringBuffer stringBuffer = StringBuffer(ByteUtil.byte2Hex(step));
    if (error != null) {
      stringBuffer.write(split);
      stringBuffer.write(ByteUtil.byte2Hex(error!));
    } else if (fileOperationStepError != null) {
      stringBuffer.write(split);
      stringBuffer.write(ByteUtil.byte2Hex(fileOperationStepError!.step));
      stringBuffer.write(split);
      stringBuffer.write(ByteUtil.byte2Hex(fileOperationStepError!.error));
      if (fileOperationStepError!.extra != FileOperationStepError.noExtra) {
        stringBuffer.write(split);
        stringBuffer.write(ByteUtil.numberLe2HexString(fileOperationStepError!.extra));
      }
    }
    return stringBuffer.toString().toUpperCase();
  }
}
